package com.googlecode.horncomb.web.spring.mvc;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.TypeMismatchException;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;

import com.googlecode.horncomb.web.util.ModelAndViewUtil;

/**
 * @author Daniel
 */
public class FormServiceController<MainServiceType extends Object> extends
		FormEventController {
	protected MainServiceType formService = null;
	protected String showFormServiceMethodName = "showReference"; //$NON-NLS-1$
	protected String getCommandServiceMethodName = null;
	private Map<String, Method> serviceMethodMap = new HashMap<String, Method>();
	private Boolean paged = true;

	protected FormServiceController() {
		super();
	}

	@SuppressWarnings("unchecked")
	@Override
	protected void initApplicationContext() {
		super.initApplicationContext();
		// cache service method
		for (Method method : this.formService.getClass().getMethods()) {
			Class<?> returnType = method.getReturnType();
			if (!"wait".equals(method.getName())
					&& !"notifyAll".equals(method.getName())) {
				if ((StringUtils.hasText(this.getCommandServiceMethodName) && this.getCommandServiceMethodName
						.equals(method.getName()))
						|| Map.class.equals(returnType)
						|| void.class.equals(returnType)) {
					if (method.getParameterTypes().length > 0
							&& this.getCommandClass().isAssignableFrom(
									method.getParameterTypes()[0])) {
						this.serviceMethodMap.put(method.getName(), method);
					}
				}
			}
		}
	}

	@Override
	protected void referenceCommand(HttpServletRequest request, Object command)
			throws Exception {
		if (StringUtils.hasText(this.getCommandServiceMethodName)) {
			Object returnValue = this.invokeServiceMethod(
					this.getCommandServiceMethodName, command);
			if (returnValue != null) {
				if (!ClassUtils.isAssignableValue(this.getCommandClass(),
						returnValue)) {
					throw new TypeMismatchException(returnValue, this
							.getCommandClass());
				}
				BeanUtils.copyProperties(returnValue, command);
			}
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	protected Map<String, Object> referenceData(HttpServletRequest request,
			Object command, Errors errors) throws Exception {
		Object returnValue = this.invokeServiceMethod(
				this.showFormServiceMethodName, command);
		if (returnValue instanceof Map) {
			return (Map) returnValue;
		} else {
			return null;
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public ModelAndView defaultEventHandle(HttpServletRequest request,
			HttpServletResponse response, Object command, BindException errors)
			throws Exception {
		String event = this.resolveRequestEvent(request, command);
		// get service method name
		String svrsMethodName = this.resolveServiceMethodName(event);
		Object returnValue = this.invokeServiceMethod(svrsMethodName, command);

		Map controlModel = null;
		if (returnValue == null || !(returnValue instanceof Map)) {
			this.logger.debug("ignore return value");
		} else {
			controlModel = (Map) returnValue;
		}
		ModelAndView mav = this.getSuccessModeAndView(request, command, errors,
				controlModel);
		return mav;
	}

	@SuppressWarnings("unchecked")
	protected ModelAndView getSuccessModeAndView(HttpServletRequest request,
			Object command, BindException errors, Map returnModel)
			throws Exception {
		Object view = this.resolveEventView(request, command, errors,
				returnModel);
		if (view != null) {
			if (view instanceof String) {
				String viewName = StringUtils.trimWhitespace((String) view);
				if (StringUtils.hasText(viewName)) {
					if (StringUtils.hasText(getSuccessView())
							&& getSuccessView().equals(getFormView())) {
						this.setSuccessView(null);
					}
					// 原则上优先服从successView，然后是formView
					if (StringUtils.hasText(getSuccessView())
							&& getSuccessView().equals(view)) {
						Map<String, Object> ref = this.getSuccessViewReference(
								request, command, errors);
						ModelAndView mav = ModelAndViewUtil.createModelAndView(
								view, returnModel);
						if (ref != null) {
							mav.addAllObjects(ref);
						}
						return mav;
					} else if (getFormView() != null
							&& getFormView().equals(view)) {
						return showForm(request, errors, getFormView(),
								returnModel);
					}
				}
			}
			if (returnModel == null) {
				returnModel = errors.getModel();
			} else {
				returnModel.putAll(errors.getModel());
			}
			return ModelAndViewUtil.createModelAndView(view, returnModel);
		} else {
			// 如果ViewResolver没有解析出可以用的View，那么就用基本方式处理。
			return super.getSuccessModeAndView(request, command, errors,
					returnModel);
		}
	}

	/**
	 * 调用服务的具体方法
	 * @param method
	 * @param command
	 * @return
	 * @throws Exception
	 */
	protected Object invokeServiceMethod(String methodName, Object command)
			throws Exception {
		Assert.hasText(methodName);
		Method method = this.serviceMethodMap.get(methodName);
		if (method == null) {
			throw new NoSuchRequestHandlingMethodException(methodName,
					this.formService.getClass());
		}
		return method.invoke(this.formService, new Object[] { command });
	}

	protected String resolveServiceMethodName(String event) {
		return event;
	}

	public void setFormService(MainServiceType mainService) {
		Assert.notNull(mainService);
		this.formService = mainService;
	}

	public Boolean isPaged() {
		return paged;
	}

	public void setPaged(Boolean paged) {
		this.paged = paged;
	}

	/**
	 * @param showFormServiceMethodName
	 *            the showFormServiceMethodName to set
	 */
	public void setShowFormServiceMethodName(String showFormServiceMethodName) {
		Assert.isTrue(StringUtils.hasText(showFormServiceMethodName));
		this.showFormServiceMethodName = showFormServiceMethodName;
	}

	/**
	 * @param getCommandServiceMethodName
	 *            the getCommandServiceMethodName to set
	 */
	public void setGetCommandServiceMethodName(
			String getCommandServiceMethodName) {
		Assert.isTrue(StringUtils.hasText(getCommandServiceMethodName));
		this.getCommandServiceMethodName = getCommandServiceMethodName;
	}
}
